<?php

define('CDB_STORE_NO_SELECT_INSERTED', 0x02);

//+----------------------------------------------------------------------------+
//+    CxDatabase
//+----------------------------------------------------------------------------+

abstract class CxDatabase
{
    protected $conn =  null;  //  

    protected $name =  null;  //  

    protected $asep =  '___';  //  

    /**
     * 
     * Specify $conn to reuse db connection
     * or another CDB object.
     */
    function __construct ($conn = null)
    {

		if (is_resource($conn))
			$this->conn = $conn;
	    else if (is_a($conn, __class__)) {
			$this->conn = $conn->conn;
			$this->name = $conn->name;
		}
    }

    /**
     * 
     * @code
     * array (
     * table_name => array ( // tprop
     * 'fields' => array (
     * array ( // row
     * field_name => value,
     * ...
     * ),
     * ...
     * ),
     * 'unique' => array (
     * field_name,
     * ...
     * ),
     * 'where' => array (
     * operation => array (
     * field_name => condition,
     * ...
     * condition_string,
     * ...
     * operation => array (
     * ...
     * ),
     * ...
     * ),
     * ...
     * ),
     * 'tail' => ...
     * ),
     * ...
     * )
     * @endcode
     */
    function set ($data)
    {

		$this->data = $data;
    }

    function insert ($data = NULL)
    {
        if (!$data) $data =& $this->data;
        foreach ($data as $table_name => $tprop) {
            foreach ($tprop['fields'] as $row) {
        	    $field_names = array();
        	    $field_values = array();
                foreach ($row as $fname => $fvalue) {
                 	$field_names[] = $fname;
                 	$field_values[] = $this->quoted_value($fvalue);
                }
        		$this->query($this->stm_insert($table_name,
        			$field_names, $field_values, $tprop['tail']));
            }
        }
    }

    /**
     *  end function insert
     */
    protected function stm_insert ($table_name, $field_names, $field_values, $tail = NULL)
    {
        // may be overridden for specific dbms
        if (is_array($field_names))
            $field_names = join(',', $field_names);
        if (is_array($field_values))
            $field_values = join(',', $field_values);
        return "insert into $table_name ($field_names) values ($field_values)"
            .$this->stm_tail($tail);
    }

    function update ($data = NULL)
    {

        if (!$data) $data =& $this->data;
        foreach ($data as $table_name => $tprop) {
            $condition_list = array();
            $this->build_cond_list($condition_list,
                $table_name, $tprop['where']);
            foreach ($tprop['fields'] as $nrow => $row) {
                $c_list = $condition_list;
       			$field_assignments = array();
                foreach ($row as $fname => $fvalue) {
                    if (!@in_array($fname, $tprop['unique']))
                     	$field_assignments[] = "$fname=".
                            $this->quoted_value($fvalue);
                    else {
                     	$c_list[] = "$fname=".
                            $this->quoted_value($fvalue);
                     	if (@array_key_exists($fname, $tprop['uniqval'][$nrow]))
                            $field_assignments[] = "$fname=".
                                $this->quoted_value($tprop['uniqval'][$nrow][$fname]);
                    }
                }
    			$this->query($this->stm_update($table_name,
    				$field_assignments, $c_list, $tprop['tail']));
            }
        }
    }

    /**
     *  end function update
     */
    protected function stm_update ($table_name, $field_assignments, $condition_list, $tail = NULL)
    {

		// may be overridden for specific dbms
        if (is_array($field_assignments))
            $field_assignments = join(',', $field_assignments);
		return "update $table_name set $field_assignments"
            .$this->stm_where($condition_list)
            .$this->stm_tail($tail);
    }

    function store ($data = NULL, $flags = 0)
    {

        $result = array();
        if (!$data) $data =& $this->data;
        foreach ($data as $table_name => $tprop) {
            $condition_list = array();
            $this->build_cond_list($condition_list,
                $table_name, $tprop['where']);
            foreach ($tprop['fields'] as $row) {
                $c_list = $condition_list;
                foreach ($tprop['unique'] as $fname) {
                    if (isset($row[$fname]))
                 	    $c_list[] = "$table_name.$fname="
    					   .$this->quoted_value($row[$fname]);
                }
                if (!$c_list) {
                    trigger_error("$table_name: no condition computed. Skip.");
                    break;
                }
    			// get unique field values and check if record exists
    			$check_stm = $this->stm_select($table_name,
    				$tprop['unique'], $c_list);
    			$uniq_out = $this->query_first($check_stm);
    			if ($uniq_out) {
    				$this->update(array($table_name => array (
    					'fields' => array(array_merge($row,$uniq_out)),
    					'unique' => array(),
    					'where'  => $c_list)));
    			}
    			else {
    				$this->insert(array($table_name => array (
    					'fields' => array($row))));
                    ($flags & CDB_STORE_NO_SELECT_INSERTED) or
                        $uniq_out = $this->query_first($check_stm);
    			}
       			$result[$table_name]['fields'][] = $uniq_out;
            }
		}
		return $result;
    }

    /**
     *  end function store
     */
    function delete ($data = NULL)
    {

        if (!$data) $data =& $this->data;
        foreach ($data as $table_name => $tprop) {
            $condition_list = array();
            $this->build_cond_list($condition_list,
                $table_name, $tprop['where']);
            if (!isset($tprop['fields']))
    			$this->query($this->stm_delete($table_name,
    				$condition_list));
            else foreach ($tprop['fields'] as $row) {
                $c_list = $condition_list;
                foreach ($tprop['unique'] as $fname) {
                    if (isset($row[$fname]))
                 	    $c_list[] = "$table_name.$fname="
                            .$this->quoted_value($row[$fname]);
                }
                if (!$c_list) {
                    trigger_error("$table_name: no condition computed. Skip.");
                    break;
                }
    			$this->query($this->stm_delete($table_name,
    				$c_list, $tprop['tail']));
            }
        }
    }

    /**
     *  end function delete
     */
    protected function stm_delete ($table_name, $condition_list, $tail = NULL)
    {

		// may be overridden for specific dbms
		return "delete from $table_name"
            .$this->stm_where($condition_list)
            .$this->stm_tail($tail);
    }

    function select (& $data)
    {

	    $table_list = array();
       	$field_list = array();
       	$condition_list = array();

        if (!$data) $data =& $this->data;
        foreach ($data as $table_name => $tprop) {
            $this->build_cond_list($condition_list,
                $table_name, $tprop['where']);
   			$table_list[] = $table_name;
   			if (is_array($tprop['fields'])) {
                foreach ($tprop['fields'][0] as $fname => $fvalue) {
                    $tmpfr = !is_object($fvalue) ? "$table_name.$fname" : //???//
                        $fvalue->expr("$table_name.$fname");
                    $field_list[] = "$tmpfr as $table_name$this->asep$fname";
                }
            }
   			if (is_array($tprop['group'])) {
                $group = array();
                foreach ($tprop['group'] as $fname)
                    $group[] = "$table_name.$fname";
            }
   			if (is_array($tprop['order'])) {
                $order = array();
                foreach ($tprop['order'] as $fname)
                    $order[] = "$table_name.$fname";
            }
		}
		$rs = $this->query($this->stm_select($table_list,
            $field_list, $condition_list, $group, $order));
        $rcount = 0;
		while ($rec = $this->fetch($rs)) {
            $row = array();
            foreach ($rec as $key => $value) {
                list($table_name, $field_name) = explode($this->asep, $key);
                if (!$field_name) continue/*???*/;
                $row[$field_name] = $value;
            }
            $data[$table_name]['fields'][$rcount++] = $row;
        }
        $this->free($rs);
		return $rcount;
    }

    protected function stm_select ($table_list, $field_names, $condition_list = NULL, $group = NULL, $order = NULL, $tail = NULL)
    {

		// may be overridden for specific dbms
		if (is_array($field_names))
            $field_names = join(',', $field_names);
		if (is_array($table_list))
            $table_list = join(',', $table_list);
	    return "select $field_names from $table_list"
            .$this->stm_where($condition_list)
            .$this->stm_group_by($group)
            .$this->stm_order_by($order)
            .$this->stm_tail($tail);
    }

    protected static function stm_where ($condition_list)
    {

		// may be overridden for specific dbms
	    if (!$condition_list) return '';
		if (is_array($condition_list))
			$condition_list = '('.join(')and(', $condition_list).')';
        return " where $condition_list";
    }

    protected static function stm_group_by ($group)
    {

		// may be overridden for specific dbms
	    if (!$group) return '';
		if (is_array($group))
			$group = join(',', $group);
        return " group by $group";
    }

    protected static function stm_order_by ($order)
    {

		// may be overridden for specific dbms
	    if (!$order) return '';
		if (is_array($order))
			$order = join(',', $order);
        return " order by $order";
    }

    protected static function stm_tail ($tail)
    {

		// may be overridden for specific dbms
	    return $tail ? " $tail" : '';
    }

    /**
     * TODO: handle '=null' and 'is null'
     */
    protected static function quoted_value ($value)
    {

		// may be overridden for specific dbms
		if (!isset($value)) return 'null';
		if (is_string($value)) return "'$value'";
		if (is_object($value)) return $value->expr();
        return $value;
    }

    /**
     * ========================================================================
     */
    function fetch_array ($stm_select)
    {

        $result = array();
		$rs = $this->query($stm_select);
		while ($rec = $this->fetch($rs)) {
            $result[] = $rec;
        }
        $this->free($rs);
        return $result;
    }

    function query_first ($stm_select, $fname = NULL)
    {

		$rs = $this->query($stm_select);
		$result = $this->fetch($rs);
        $this->free($rs);
        return $fname ? $result[$fname] : $result;
    }

    function build_cond_list (& $condition_list, $table_name, $where)
    {

        if (!$where) return NULL;
		if (!$condition_list)
            $condition_list = array();
		if (!is_array($where))
            $condition_list[] = $where;
        else foreach ($where as $key => $cond) {
            if (!isset($cond))
                throw new ExError("Null condition for $key");//???//continue;
			if (is_array($cond)) {
                $tmp = NULL;
				$this->build_cond_list($tmp, $table_name, $cond);
				if (is_int($key)) $key = 'and'; // 15.08.2008 12:05
       			if ($tmp) $condition_list[] = '('.join(")$key(", $tmp).')';
			}
			else if (is_int($key)) {
			    $condition_list[] = (string)$cond;
			}
			else { // 16.08.2008 15:55
                $table_name and $table_name .= '.';
			    $condition_list[] = "$table_name$key="
                    .$this->quoted_value($cond);
			}
        }
    }

    /**
     *  end function build_cond_list
     */
    function walk ($stm_select, $fn, $arg = NULL)
    {

        $result = array();
		$rs = $this->query($stm_select);
        while ($rec = $this->fetch($rs)) {
            $result[] = call_user_func($fn, $rec, $arg);
        }
        $this->free($rs);
        return $result;
    }

    /**
     * ========================================================================
     * / on success: set established db connection.
     */
    abstract function connect ($host, $user, $password);
    abstract function disconnect ();
    /**
     * / on success: select db $name.
     */
    abstract function select_db ($name);
    /**
     * / on success: return query result.
     */
    abstract function query ($sql);
    /**
     * / on success: return query result.
     */
    abstract function fetch ($rs);
    abstract function seek ($rs, $n);
    abstract function free ($rs);
    /**
     * / on success: return number of rows in recordset.
     */
    abstract function get_num_rows ($rs);
    abstract function get_last_gen ($gen = NULL);
    abstract function get_errmsg ();
    abstract function get_errcode ();
}

//+----------------------------------------------------------------------------+
//+    CxDBExpression
//+----------------------------------------------------------------------------+

/**
 * ========================================================================
 */
class CxDBExpression
{
    protected $expr;  //  

    function __construct ($expr)
    {

        $this->expr = $expr;
    }

    function expr ()
    {

        //???//if (!func_num_args()) return $this->expr;
        $args = func_get_args();
        return vsprintf($this->expr, $args);
    }

}



/* to provide a compatibility with PEAR DB portable error codes */

/**
 * The code returned by many methods upon success
 */
define('DB_OK', 1);

/**
 * Unknown error
 */
define('DB_ERROR', -1);

/**
 * Syntax error
 */
define('DB_ERROR_SYNTAX', -2);

/**
 * Tried to insert a duplicate value into a primary or unique index
 */
define('DB_ERROR_CONSTRAINT', -3);

/**
 * An identifier in the query refers to a non-existant object
 */
define('DB_ERROR_NOT_FOUND', -4);

/**
 * Tried to create a duplicate object
 */
define('DB_ERROR_ALREADY_EXISTS', -5);

/**
 * The current driver does not support the action you attempted
 */
define('DB_ERROR_UNSUPPORTED', -6);

/**
 * The number of parameters does not match the number of placeholders
 */
define('DB_ERROR_MISMATCH', -7);

/**
 * A literal submitted did not match the data type expected
 */
define('DB_ERROR_INVALID', -8);

/**
 * The current DBMS does not support the action you attempted
 */
define('DB_ERROR_NOT_CAPABLE', -9);

/**
 * A literal submitted was too long so the end of it was removed
 */
define('DB_ERROR_TRUNCATED', -10);

/**
 * A literal number submitted did not match the data type expected
 */
define('DB_ERROR_INVALID_NUMBER', -11);

/**
 * A literal date submitted did not match the data type expected
 */
define('DB_ERROR_INVALID_DATE', -12);

/**
 * Attempt to divide something by zero
 */
define('DB_ERROR_DIVZERO', -13);

/**
 * A database needs to be selected
 */
define('DB_ERROR_NODBSELECTED', -14);

/**
 * Could not create the object requested
 */
define('DB_ERROR_CANNOT_CREATE', -15);

/**
 * Could not drop the database requested because it does not exist
 */
define('DB_ERROR_CANNOT_DROP', -17);

/**
 * An identifier in the query refers to a non-existant table
 */
define('DB_ERROR_NOSUCHTABLE', -18);

/**
 * An identifier in the query refers to a non-existant column
 */
define('DB_ERROR_NOSUCHFIELD', -19);

/**
 * The data submitted to the method was inappropriate
 */
define('DB_ERROR_NEED_MORE_DATA', -20);

/**
 * The attempt to lock the table failed
 */
define('DB_ERROR_NOT_LOCKED', -21);

/**
 * The number of columns doesn't match the number of values
 */
define('DB_ERROR_VALUE_COUNT_ON_ROW', -22);

/**
 * The DSN submitted has problems
 */
define('DB_ERROR_INVALID_DSN', -23);

/**
 * Could not connect to the database
 */
define('DB_ERROR_CONNECT_FAILED', -24);

/**
 * The PHP extension needed for this DBMS could not be found
 */
define('DB_ERROR_EXTENSION_NOT_FOUND',-25);

/**
 * The present user has inadequate permissions to perform the task requestd
 */
define('DB_ERROR_ACCESS_VIOLATION', -26);

/**
 * The database requested does not exist
 */
define('DB_ERROR_NOSUCHDB', -27);

/**
 * Tried to insert a null value into a column that doesn't allow nulls
 */
define('DB_ERROR_CONSTRAINT_NOT_NULL',-29);

?>